Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 6 Vererbung, Polymorphie und Schnittstellen
  gp 6.1 Basisklassen und abgeleitete Klassen
    gp 6.1.1 Ableiten einer Klasse
    gp 6.1.2 Klassen, die nicht vererben können
    gp 6.1.3 Zusammenfassung
  gp 6.2 Konstruktoren in abgeleiteten Klassen
    gp 6.2.1 Die Konstruktoren der Klasse »GraphicCircle«
    gp 6.2.2 Der Zugriffsmodifizierer »protected«
    gp 6.2.3 Konstruktorverkettung
    gp 6.2.4 Destruktor-Verkettung
    gp 6.2.5 Der Stand des Projekts »CircleApplication«
    gp 6.2.6 Zusammenfassung
  gp 6.3 Die Methoden in einer abgeleiteten Klasse
    gp 6.3.1 Geerbte Methoden mit »new« verdecken
    gp 6.3.2 Überladen einer Basisklassenmethode
  gp 6.4 Ereignisse in der Vererbung
  gp 6.5 »Hat-eine«-Beziehungen (Aggregation)
    gp 6.5.1 Weiterleitung einer internen Objektreferenz
    gp 6.5.2 Verbergen des internen Objekts
    gp 6.5.3 Innere Klassen
  gp 6.6 Typumwandlung von Objektvariablen
    gp 6.6.1 Die implizite Typumwandlung von Objektreferenzen
    gp 6.6.2 Die explizite Typumwandlung von Objektreferenzen
    gp 6.6.3 Zusammenfassung
  gp 6.7 Abstrakte Klassen und Methoden
    gp 6.7.1 Abstrakte Definitionen
  gp 6.8 Polymorphie
    gp 6.8.1 Virtuelle Methoden
    gp 6.8.2 Inhomogene Mengen
    gp 6.8.3 Verdecken und Überschreiben geerbter Methoden
    gp 6.8.4 Überschreiben der Methode »ToString()« der Klasse »Object«
    gp 6.8.5 Versiegelte Methoden
    gp 6.8.6 Zusammenfassung
  gp 6.9 Erweiterung der Klassenhierarchie »CircleApplication«
    gp 6.9.1 Die Klasse »GeometricObject«
  gp 6.10 Schnittstellen
    gp 6.10.1 Einführung in die Schnittstellen
    gp 6.10.2 Schnittstellendefinition
    gp 6.10.3 Schnittstellenimplementierung
    gp 6.10.4 Typumwandlung mit dem »as«-Operator
    gp 6.10.5 Abstrakte Klassen vs. Schnittstellen
    gp 6.10.6 Zusammenfassung


Galileo Computing

6.8 Polymorphie  downtop

Ruft ein Client eine Methode in einem Subklassenobjekt auf, wird in der Subklasse geprüft, ob sie eine Methode mit diesem Namen und exakt denselben Parametern enthält. Handelt es sich tatsächlich um eine Subklassenmethode, wird sie ausgeführt, andernfalls wird der Aufruf an die direkte Basisklasse weitergeleitet, in der nach denselben Kriterien gesucht wird.

Gegebenenfalls durchläuft der Mechanismus die gesamte Vererbungslinie bis hin zur .NET-Basisklasse Object. Wird in der gesamten Vererbungskette keine passende Methode gefunden, kommt es zu einem Laufzeitfehler. Befinden sich zwei oder mehr gleichnamige Methoden mit identischer Signatur in der Vererbungskette, wird die Methode ausgeführt, die zuerst die Bedingungen erfüllt. Dazu ein Beispiel:


class ClassA {
  public void MyMethod() { 
    Console.WriteLine("MyMethod von ClassA");
  }
}
class ClassB : ClassA {
  public new void MyMethod() {
    Console.WriteLine("MyMethod von ClassB");
  }
}

Mit


ClassB obj = new ClassB();
obj.MyMethod();

wird die in der Subklasse definierte Methode ausgeführt und im Befehlsfenster


MyMethod von ClassB

angezeigt.

Schlagen wir jetzt einen anderen Weg ein und instanziieren ein Objekt der abgeleiteten Klasse auf folgende Weise:


ClassA obj = new ClassB();

Im Vergleich zu der vorhergehenden Instanziierung ist nun eine Variable vom Typ der Basisklasse deklariert, der wir die Referenz auf ein Objekt der abgeleiteten Klasse zuweisen. Weil ein ClassB-Objekt gleichzeitig auch ein Objekt seiner Basisklasse ist, ist diese Zuweisung nicht zu beanstanden. Rufen wir jetzt erneut die Methode MyMethod auf, erhalten wir ein völlig anderes Ergebnis im Konsolenfenster. Es wird nicht mehr die Methode der abgeleiteten Klasse, sondern die der Basisklasse ausgeführt:


MyMethod von ClassA

Das Ergebnis ist zwar nicht spektakulär, hat aber weit reichende Konsequenzen. Wir müssen uns nämlich die Frage stellen, ob die Ausgabe das ist, was wir erreichen wollten. Vermutlich nicht, denn eigentlich sollte doch die klassenspezifische Methode in ClassB, der abgeleiteten Klasse, ausgeführt werden.

Das Beispiel macht deutlich, welchen Nebeneffekt das Überdecken einer Methode der Basisklasse haben kann: Der Compiler betrachtet das Objekt, als wäre es vom Typ der Basisklasse, und ruft die unter Umständen aus logischer Sicht sogar fehlerhafte Methode in der Basisklasse auf.

Ursächliches Problem ist das statische Binden des Methodenaufrufs an die Basisklasse. Statisches Binden heißt, dass die auszuführende Operation bereits zur Kompilierzeit festgelegt wird. Der Compiler stellt fest, von welchem Typ das Objekt ist, auf das die Methode aufgerufen wird, und erzeugt den entsprechenden Code. Das statische Binden führt dazu, dass die verdeckte Methode der Basisklasse aufgerufen wird, obwohl eigentlich die neue Methode in der abgeleiteten Klasse erforderlich wäre.


Galileo Computing

6.8.1 Virtuelle Methoden  downtop

Um auf die Referenz eines Basisklassentyps die Methode in der abgeleiteten Klasse aufrufen zu können, muss die statische Bindung aufgehoben werden. Dazu dienen die beiden Schlüsselwörter virtual und override. Definieren Sie in der Basisklasse eine Methode virtual, kann sie von einer Subklassenmethode mit override überschrieben werden. Der Compiler bindet dann nicht mehr statisch zur Kompilierzeit, sondern dynamisch zur Laufzeit.

Ergänzen Sie die Definition der Methode MyMethod in der Basisklasse um virtual:


// die Basisklasse
class ClassA {
  public virtual void MyMethod() {
    Console.WriteLine("MyMethod von ClassA");
  }
}

Eine ableitende Klasse, die eine virtuelle Methode der Basisklasse durch eine eigene Implementierung überschreiben möchte, kann die Methodendefinition um den Modifizierer override erweitern:


// die abgeleitete Klasse
class ClassB : ClassA {
  public override void MyMethod() {
    Console.WriteLine("MyMethod von ClassB");
  }
}

Wird nun mit


ClassA obj = new ClassB();
obj.MyMethod();

derselbe Testcode wie oben ausgeführt, ist das Ergebnis ein völlig anderes: Erst wenn es zur Laufzeit zum Aufruf der Methode MyMethod kommt, ermittelt die Laufzeitumgebung den tatsächlichen Typ der Referenz obj und stellt fest, dass es sich um ClassB handelt. Deren Methode wird aufgerufen und gibt im Konsolenfenster


MyMethod von ClassB

aus. Anscheinend ist das Objekt obj in der Lage zu entscheiden, welche Methode auf sich selbst anzuwenden ist – unabhängig davon, wo es in der Vererbungslinie steht. Diese Fähigkeit wird als Polymorphie bezeichnet und ist neben der Kapselung und der Vererbung die dritte Stütze der objektorientierten Programmierung.


Polymorphie bezeichnet ein Konzept der Objektorientierung, das besagt, dass Objekte bei gleichen Methodenaufrufen unterschiedlich reagieren können. Objekte verschiedener Typen können dabei unter einem gemeinsamen Oberbegriff betrachtet werden. Die Polymorphie sorgt dafür, dass der Methodenaufruf automatisch bei der richtigen Methode landet – nämlich der, die in der Klasse des Objekts implementiert ist.

Polymorphie arbeitet mit dynamischer Bindung. Der Aufrufcode wird nicht zur Kompilierzeit erzeugt, sondern erst zur Laufzeit der Anwendung, wenn die konkreten Typinformationen vorliegen, um die tatsächlich aufzurufende Methode zu bestimmen. Im Gegensatz dazu legt die statische Bindung die auszuführende Operation bereits zur Kompilierzeit fest.

Beachten Sie, dass eine statische Methode nicht virtuell sein kann. Ebenso ist eine Kombination des Schlüsselworts virtual mit abstract oder override nicht zulässig. Hinter der Definition einer virtuellen Methode verbirgt sich die Absicht, polymorphes Verhalten zu ermöglichen. Daher macht es auch keinen Sinn, ein privates Klassenmitglied virtual zu deklarieren – es kommt zu einem Kompilierfehler. new und override dürfen nicht für dasselbe Member verwendet werden, sie schließen sich gegenseitig aus.


Galileo Computing

6.8.2 Inhomogene Mengen  downtop

In inhomogenen Mengen, die sich in einer Basisklasse unter einem gemeinsamen Oberbegriff zusammenfassen lassen, spielt die Polymorphie eine wichtige Rolle, um die jeweils richtige Methode aufzurufen. Inhomogene Mengen werden durch Arrays vom Typ einer Basisklasse abgebildet, denen Objekte derselben oder einer abgeleiteten Klasse zugewiesen werden.

Betrachten wir die Klassenhierarchie der Luftfahrzeuge und hier insbesondere der Methode Starten der Klasse Luftfahrzeug. Im Abschnitt 6.7 haben Sie gesehen, dass es sich anbietet, diese Methode abstrakt zu definieren. Im folgenden Beispiel wird Starten in der Klasse Luftfahrzeug implementiert, allerdings virtuell.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 6\Polymorphie 
// --------------------------------------------------------------
class Luftfahrzeug {
  public virtual void Starten() {
    Console.WriteLine("Das Luftfahrzeug startet");
  }
}
class Hubschrauber : Luftfahrzeug {
  public override void Starten() {
    Console.WriteLine("Der Hubschrauber startet");
  }
}
class Zeppelin : Luftfahrzeug {
  public override void Starten() {
    Console.WriteLine("Der Zeppelin startet");
  }
}
class Flugzeug : Luftfahrzeug { }

Zeppelin, Hubschrauber und Flugzeug leiten die Klasse Luftfahrzeug ab. Während für ein herkömmliches Flugzeug die Implementierung der Methode in Luftfahrzeug ausreichend ist, überschreiben die beiden anderen Subklassen mit override die geerbte Methode Starten.

Müssen mehrere verschiedene Typen aus einer Vererbungslinie gleichzeitig verwaltet werden, bietet es sich an, dafür ein Array vorzusehen. Sinnvollerweise wird das Array von dem Typ deklariert, welcher der allgemeinste aller verwalteten Objekte ist. In unserem Fall handelt es sich demnach um Luftfahrzeug:


Luftfahrzeug[] lfzg = new Luftfahrzeug[11];

Das Array lfzg kann maximal elf Objekte vom Typ Luftfahrzeug aufnehmen, einschließlich der abgeleiteten Typen. In beliebiger Reihenfolge lassen sich den Array-Elementen Referenzen auf die Typen zuweisen. Program enthält eine statische Methode namens MakeType, in der die Bestimmung des Typs dem Zufallszahlengenerator Random überlassen wird.


class Program {
  static void Main(string[] args) {
    Luftfahrzeug[] lfzg = MakeType();
    // Inhalt des Arrays lfzg ausgeben
    for(int i = 0; i <= 10; i++)
      Console.Write("Index = {0} / Typ: {1}\n", i, lfzg[i].ToString());
    Console.WriteLine("----------------------------------");
    // alle Hubschrauber starten
    for(int i = 0; i <= 10; i++) {
      if(lfzg[i] is Hubschrauber) {
        Console.Write("Index = {0} / ", i); 
        lfzg[i].Starten();
      }
    }
    Console.ReadLine();
  }
  // ein Array mit Objekten vom Typ Luftfahrzeug füllen
  static Luftfahrzeug[] MakeType()    {
    Luftfahrzeug[] arrLfzg = new Luftfahrzeug[11];
    Random rnd = new Random();
    int type;
    for(int i = 0; i <= 10; i++) {
      // Zufallszahl im Bereich 0 <= type < 3
      type = rnd.Next(3);
      switch(type) {
        case 0:
          arrLfzg[i] = new Zeppelin();
          break;
        case 1:
          arrLfzg[i] = new Hubschrauber();
          break;
        case 2:
          arrLfzg[i] = new Flugzeug();
          break;
      }
    }
    // Referenz des Arrays an den Aufrufer liefern
    return arrLfzg;
  }
}

In Main wird der Rückgabewert von MakeType direkt einem Array zugewiesen:


Luftfahrzeug[] lfzg = MakeType();

Anschließend werden die Elemente inklusive der Angabe der den Indizes zugeordneten Typen an der Konsole ausgegeben. Um das polymorphe Verhalten zu demonstrieren, werden exemplarisch alle Hubschrauber gestartet. Dazu muss zuerst der Typ, der sich hinter jedem Array-Element verbirgt, mit


if(lfzg[i] is Hubschrauber)...

ermittelt werden. Liefert die Typbestimmung true, wird mit


lfzg[i].Starten();

der Hubschrauber gestartet.

Durch die Definition virtual in der Basisklasse gestehen wir der Methode polymorphes Verhalten zu. Unabhängig davon, ob sich hinter einem Array-Element, das vom Typ der Basisklasse Luftfahrzeug ist, tatsächlich ein Flugzeug, Hubschrauber oder Zeppelin verbirgt, wird der Aufruf immer polymorph an die »richtige« Methode weitergeleitet.


Galileo Computing

6.8.3 Verdecken und Überschreiben geerbter Methoden  downtop

Um polymorphes Verhalten zu ermöglichen, muss eine Methode virtual definiert sein. In einer abgeleiteten Klasse kann dieses Angebot angenommen oder abgelehnt werden.

gp  Implementieren Sie in der abgeleiteten Klasse die geerbte Methode mit dem Schlüsselwort override, wird die ursprüngliche Methode überschrieben – die abgeleitete Klasse akzeptiert das Angebot der Basisklasse. Ein Aufruf an eine Referenz des Oberbegriffs wird polymorph an den sich tatsächlich dahinter verbergenden Typ weitergeleitet.
gp  In der abgeleiteten Klasse kann eine virtuelle Methode auch mit dem Modifizierer new ausgeblendet werden. Dann verdeckt die geerbte Methode in der Subklasse die Implementierung in der Basisklasse und zeigt kein polymorphes Verhalten mehr.

Es ist sogar möglich, innerhalb einer Vererbungskette ein gemischtes Verhalten von Ausblendung und Überschreibung vorzusehen, wie das folgende Codefragment zeigt:


class ClassA {
  public virtual void TestMethod() { }
}
class ClassB : ClassA {
  public override void TestMethod () { }
}
class ClassC : ClassB {
  public new void TestMethod () { }
}

ClassA bietet die virtuelle Methode TestMethod an. ClassB als Subklasse von ClassA überschreibt mit override, die zweite Ableitung in der ClassC verdeckt jedoch nur noch mit new.

Wenn Sie nun nach der Zuweisung


ClassA obj = new ClassC();

auf die Referenz obj die Methode TestMethod aufrufen, wird die Methode TestMethod in ClassB ausgeführt, da diese die aus ClassA geerbte polymorph überschreibt. TestMethod zeigt aber in der Klasse ClassC wegen des Modifizierers new kein polymorphes Verhalten mehr.

Das Überschreiben einer mit new überdeckenden Methode mit override ist nicht möglich:


class ClassB : ClassA {
  public new void TestMethod() { }
}
class ClassC : ClassB {
  public override void TestMethod () { }
}

Der C#-Compiler wird nun eine Fehlermeldung ausgeben.


Galileo Computing

6.8.4 Überschreiben der Methode »ToString()« der Klasse »Object«  downtop

Die Klasse Object, aus der jede Klasse des .NET Frameworks abgeleitet wird, vererbt jeder Klasse eine Reihe elementarer Methoden. Eine davon, ToString, haben wir in unserem Beispiel Polymorphie dazu benutzt, um im Befehlsfenster den Typ, der sich hinter einem Array-Element verbirgt, ausgeben zu lassen.

Vielleicht gefällt uns der Rückgabewert der Standardimplementierung von ToString nicht, der immer den voll qualifizierenden Namen liefert. ToString ist ein typischer Kandidat zum polymorphen Überschreiben, um eine typspezifisch angepasste Zeichenfolge zu erhalten. In der Praxis wird das tatsächlich auch häufig gemacht. In der .NET-Dokumentation ist die Definition dieser Methode wie folgt angegeben:


public virtual string ToString();

Überschrieben werden sollte ToString in jeder Klasse mit override, um das polymorphe Verhalten durchzusetzen. Stellvertretend für die Klassen Flugzeug, Zeppelin und Hubschrauber sei an dieser Stelle nur die Ergänzung der Klasse Hubschrauber gezeigt:


class Hubschrauber : Luftfahrzeug {
  override public void Starten() {
    Console.WriteLine("Der Hubschrauber startet");
  }
  public override string ToString() {
    return "Hubschrauber";
  }
}

Geerbte virtuelle Methoden

Jede Methode einer Basisklasse wird an die abgeleitete Klasse vererbt. Ist die Methode darüber hinaus virtuell definiert, sollten Sie das als ein Angebot der Basisklasse verstehen, das Sie annehmen oder ablehnen können. Nehmen Sie das Angebot wahr, überschreiben Sie die geerbte Methode mit override und setzen damit polymorphes Verhalten durch. Sie können das Angebot auch ablehnen und dennoch die geerbte Methode in der abgeleiteten Klasse neu implementieren. Dazu gibt es den Modifizierer new, der aber bekanntermaßen nicht nur auf geerbte, virtuelle Methoden eingesetzt werden kann. Es gibt nur relativ wenig Methoden, die das Angebot virtueller Methoden auf diese Weise ausschlagen.

Ob Sie eine Methode in einer ableitbaren Klasse virtuell zur Verfügung stellen oder wie Sie auf das Angebot in der Subklasse reagieren, kann im Einzelfall unterschiedlich sein und muss eingehend geprüft werden.


Galileo Computing

6.8.5 Versiegelte Methoden  downtop

Standardmäßig können Klassen abgeleitet werden. Ist dieses Verhalten für eine bestimmte Klasse nicht gewünscht, kann die Klasse mit sealed versiegelt werden und ist dann nicht ableitbar.

In ähnlicher Weise können Sie dem weiteren Überschreiben einer Methode auch einen Riegel vorschieben, indem die Definition der Methode um den Modifizierer sealed ergänzt wird:


class ClassB : ClassA {
  public sealed override void TestMethod() {
    Console.WriteLine("TestProc in ClassB");
  }
}

Eine von ClassB abgeleitete Klasse erbt zwar die versiegelte Methode, kann sie aber selbst nicht mit override überschreiben. Es ist jedoch möglich, in einer weiter abgeleiteten Klasse eine geerbte, versiegelte Methode mit new zu überdecken, um eine typspezifische Anpassung vornehmen zu können.

Der Modifizierer sealed kann nur zusammen mit override in einer Methodensignatur einer abgeleiteten Klasse verwendet werden, wenn die Methode in der Basisklasse als virtuelle Methode bereitgestellt wird. Die Kombination sealed new ist unzulässig, ebenso das alleinige Verwenden von sealed.


Galileo Computing

6.8.6 Zusammenfassung  toptop

gp  Methoden ohne Anweisungsblock legen kein Objektverhalten fest und werden als abstrakte Methoden bezeichnet. Abstrakte Methoden werden mit dem Modifizierer abstract gekennzeichnet und hinter der Parameterliste mit einem Semikolon abgeschlossen. Statische Methoden können niemals abstrakt sein.
gp  Klassen, die mindestens eine abstrakte Methode haben, sind selbst abstrakt und müssen mit dem Modifizierer abstract signiert werden. Eine Klasse kann auch dann abstrakt sein, wenn keines ihrer Mitglieder abstrakt ist.
gp  Abstrakte Klassen können nicht instanziiert werden. Deshalb machen abstrakte Klassendefinitionen nur dann Sinn, wenn sie abgeleitet werden.
gp  Eine nichtabstrakte Klasse, die von einer abstrakten Klasse abgeleitet wird, muss alle geerbten abstrakten Methoden implementieren.
gp  Eine geerbte abstrakte Methode wird in der abgeleiteten Klasse durch eine Signatur, die den override-Modifizierer verwendet, überschrieben. Wird die geerbte abstrakte Methode nicht überschrieben, gilt auch die abgeleitete Klasse als abstrakt. Überschriebene abstrakte Methoden zeigen immer polymorhes Verhalten.
gp  Polymorphie ist einer der Stützpfeiler der objektorientierten Programmierung. Polymorphie bedeutet, dass Objekte bei gleichen Methodenaufrufen unterschiedlich reagieren können, wenn die Objekte unter einem gemeinsamen Oberbegriff betrachtet werden.
gp  Eine Methode, die sich polymorph verhalten soll, muss entweder abstract oder virtual deklariert sein. Polymorphe Methodenaufrufe werden dynamisch zur Laufzeit gebunden, wenn in der abgeleiteten Klasse die virtuelle Methode mit override überschrieben wird. Wird eine virtuelle Methode in der Subklasse mit new ausgeblendet, setzt sich das polymorphe Verhalten nicht durch, und der Methodenaufruf wird statisch gebunden.
gp  Die Modifiziererkombination sealed override einer Methode verhindert, dass sie nicht polymorph, also mit override, in einer abgeleiteten Klasse überschrieben werden kann.
 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de